package cn.org.rookie.jeesdp.authentication.configuration;

import cn.org.rookie.jeesdp.authentication.JeesdpPasswordEncoder;
import cn.org.rookie.jeesdp.core.po.User;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

/**
 * Spring Security配置类，用于配置OAuth2授权服务器和安全相关设置
 *
 * @author LHT
 * @date 2022-01-27 19:07
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration implements WebMvcConfigurer {

    /**
     * JDBC模板，用于数据库操作
     */
    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 配置OAuth2授权服务器的安全过滤器链
     *
     * @param http HttpSecurity配置对象
     * @return 构建好的安全过滤器链
     * @throws Exception 配置过程中可能抛出的异常
     */
    @Bean
    @Order(0)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {

        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer.authorizationServer();

        http.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
                .with(authorizationServerConfigurer,
                        authorizationServer -> authorizationServer.oidc(Customizer.withDefaults())
                )
                .authorizeHttpRequests(
                        authorizeRequests -> authorizeRequests
                                .requestMatchers("/actuator/**", "/static/**", "/login", "/*.ico").permitAll()
                                .requestMatchers(HttpMethod.OPTIONS).permitAll()
                                .anyRequest().authenticated()
                )
                .csrf(AbstractHttpConfigurer::disable)
                .cors(AbstractHttpConfigurer::disable)
                .formLogin(form -> form.loginPage("/login").permitAll())
                .logout(configurer -> configurer.logoutUrl("/oauth2/logout").logoutSuccessUrl("http://127.0.0.1"));
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);

        return http.build();
    }

    /**
     * 配置默认的安全过滤器链
     *
     * @param http HttpSecurity配置对象
     * @return 构建好的安全过滤器链
     * @throws Exception 配置过程中可能抛出的异常
     */
    @Bean
    @Order(1)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(
                        authorizeRequests -> authorizeRequests
                                .requestMatchers("/actuator/**", "/static/**", "/login", "/*.ico").permitAll()
                                .requestMatchers(HttpMethod.OPTIONS).permitAll()
                                .anyRequest().authenticated()
                )
                .sessionManagement(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .cors(AbstractHttpConfigurer::disable)
                //.httpBasic(Customizer.withDefaults())
                .formLogin(form -> form.loginPage("/login").permitAll())
                .logout(configurer -> configurer.logoutUrl("/oauth2/logout").logoutSuccessUrl("http://127.0.0.1"))
                .oauth2ResourceServer(
                        configurer -> configurer.jwt(Customizer.withDefaults())
                );
        return http.build();
    }

    /**
     * 创建基于JDBC的已注册客户端存储库
     *
     * @return 已注册客户端存储库实例
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        return new JdbcRegisteredClientRepository(jdbcTemplate);
    }

    /**
     * 生成RSA密钥对，用于JWT签名和验证
     *
     * @return 生成的RSA密钥对
     */
    private KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    /**
     * 创建JWK源，用于提供JWT签名和验证所需的公钥
     *
     * @return JWK源实例
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {

        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    /**
     * 创建JWT解码器，用于验证JWT令牌
     *
     * @param jwkSource JWK源
     * @return JWT解码器实例
     */
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    /**
     * 创建密码编码器，用于用户密码的加密和验证
     *
     * @return 密码编码器实例
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new JeesdpPasswordEncoder();
    }

    /**
     * 创建OAuth2令牌自定义器，用于在JWT中添加用户信息
     *
     * @return OAuth2令牌自定义器实例
     */
    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> oAuth2TokenCustomizer() {
        return context -> {
            if (context.getPrincipal().getPrincipal() instanceof User user) {
                JwtClaimsSet.Builder claims = context.getClaims();
                // 清除敏感信息
                user.setPassword("");
                user.setCreateTime(null);
                user.setModifyTime(null);
                claims.claims(map -> map.put("user", user));
            }
        };
    }
}
